package com.czx.drivingtest.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;
import com.czx.drivingtest.annotation.CustomizeCache;
import com.czx.drivingtest.constant.RedisKeys;
import com.czx.drivingtest.ex.OperateQuestionException;
import com.czx.drivingtest.model.dto.Answer;
import com.czx.drivingtest.model.dto.Question;
import com.czx.drivingtest.dao.QuestionDao;
import com.czx.drivingtest.service.QuestionService;
import com.czx.drivingtest.utils.RedisUtils;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author czx
 * @version 2020/3/12 8:52 下午
 * @since JDK12
 */
@Service
public class QuestionServiceImpl implements QuestionService {

    @Autowired
    private QuestionDao questionDao;
    @Autowired
    private RedisUtils redisUtils;

    @Override
    @CustomizeCache(value = "question", key = "#questionId", expireTimes = 120000)
    public Question getQuestionById(Integer questionId) {
        return questionDao.queryQuestionById(questionId);
    }

    @Override
    public Question getFirstQuestion(Integer subject) {
        String keyTemp = "question:subject:%d:first";
        String key = String.format(keyTemp, subject);
        Question result = (Question) redisUtils.get(key);
        if (result != null) {
            return result;
        }
        result = questionDao.queryFirstQuestion(subject);
        if (result != null) {
            redisUtils.setExpire(key, result, 3, TimeUnit.DAYS);
        }
        return result;
    }

    @Override
    public Question getNextQuestion(int questionId, Integer subject) {
        Integer nextQuestionId = questionDao.queryNextQuestionId(questionId, subject);
        if (nextQuestionId == null) {
            return null;
        } else {
            //由于直接内部调用方法不会走AOP，所以做了特殊处理
            return ((QuestionService) AopContext.currentProxy()).getQuestionById(nextQuestionId);
        }
    }

    @Override
    public Question getPreQuestion(int questionId, Integer subject) {
        Integer nextQuestionId = questionDao.queryPreQuestionId(questionId, subject);
        if (nextQuestionId == null) {
            return null;
        } else {
            //由于直接内部调用方法不会走AOP，所以做了特殊处理
            return ((QuestionService) AopContext.currentProxy()).getQuestionById(nextQuestionId);
        }
    }

    @Override
    public Integer getTotalCount(Integer subject) {
        String key = String.format(RedisKeys.TOTAL_QUESTION_COUNT, subject);
        Integer total = (Integer) redisUtils.get(key);
        if (total != null) {
            return total;
        }
        total = questionDao.queryTotal(subject);
        redisUtils.setExpire(key, total, 7, TimeUnit.DAYS);
        return total;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addQuestion(Question question) {
        checkAnswers(question.getAnswers());
        questionDao.insertQuestion(question);
        questionDao.batchInsertAnswers(question.getAnswers(), question.getQuestionId());
    }

    @Override
    public List<Question> get3Questions(Integer questionId, Integer subject) {
        if (questionId == -1) {
            //如果传入的questionId是-1, 就从第一道题开始返回给用户
            Question firstQuestion = getFirstQuestion(subject);
            Question nextQuestion = getNextQuestion(firstQuestion.getQuestionId(), subject);
            return Arrays.asList(null, firstQuestion, nextQuestion);
        } else {
            Question preQuestion = getPreQuestion(questionId, subject);
            Question question = ((QuestionService) AopContext.currentProxy()).getQuestionById(questionId);
            Question nextQuestion = getNextQuestion(questionId, subject);
            return Arrays.asList(preQuestion, question, nextQuestion);
        }
        //返回题目的顺序是 [ 前一题, 当前题, 下一题 ]
    }

    @Override
    public List<Question> getByIds(List<Integer> idList) {
        return questionDao.queryByIds(idList);
    }

    @Override
    public List<Integer> getQuestionIdList(Integer subject) {
        String key = String.format(RedisKeys.QUESTION_IDS, subject);
        @SuppressWarnings("unchecked")
        List<Integer> questionIds = redisUtils.lRange(key, 0, -1);
        if (CollUtil.isNotEmpty(questionIds)) {
            return questionIds;
        }
        questionIds = questionDao.queryQuestionIdList(subject);
        redisUtils.lRightPushAllAndExpire(key, questionIds, 2, TimeUnit.DAYS);
        return questionIds;
    }

    @Override
    public List<Question> getRandomQuestionList(Integer total, Integer subject) {
        Set<Integer> randomQuestionIds = randomQuestionIds(total, subject);
        List<Question> result = new ArrayList<>();
        for (Integer id : randomQuestionIds) {
            Question question = ((QuestionService) AopContext.currentProxy()).getQuestionById(id);
            result.add(question);
        }
        return result;
    }

    @Override
    @CustomizeCache(value = "subject:questionId", key = "#questionId", expireTimes = 120000)
    public Integer getSubjectByQuestionId(Integer questionId) {
        return questionDao.querySubject(questionId);
    }

    /**
     * 随机获取题目id
     * @param total
     * @param subject
     * @return
     */
    private Set<Integer> randomQuestionIds(Integer total, Integer subject) {
        String key = String.format(RedisKeys.QUESTION_IDS, subject);
        Long questionSize = redisUtils.lLen(key);
        Set<Integer> randomIds = new TreeSet<>();
        if (questionSize != null && questionSize > 0) {  //如果缓存中的questionIds存在,我们就去缓存中随机查找id
            int realTotal = (int) Math.min(total, questionSize);
            while (randomIds.size() < realTotal) {
                int randomIndex = RandomUtil.randomInt(Math.toIntExact(questionSize));
                Integer questionId = (Integer) redisUtils.lIndex(key, randomIndex);
                randomIds.add(questionId);
            }
        } else {
            List<Integer> questionIdList = getQuestionIdList(subject);
            int realTotal = Math.min(total, questionIdList.size());
            while (randomIds.size() < realTotal) {
                int randomIndex = RandomUtil.randomInt(questionIdList.size());
                randomIds.add(questionIdList.get(randomIndex));
            }
        }
        return randomIds;
    }

    /**
     * 校验答案中是否至少有一个正确答案
     *
     * @param answers
     */
    private void checkAnswers(List<Answer> answers) {
        int trueAnswerCount = 0;
        for (Answer answer : answers) {
            if (answer.getWasTrue()) {
                trueAnswerCount++;
            }
        }
        if (trueAnswerCount == 0) {
            throw new OperateQuestionException("请至少有一个正确答案");
        }
    }
}
